/*
 *  Copyright (c) 2016-2019 tamsong
 */

/*
 * TSH virtaul input driver for Linux
 */

/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 *
 * Should you need to contact me, the author, you can do so either by
 * e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail:
 * Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic
 */

#include <asm/uaccess.h>
#include <linux/delay.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/input.h>
#include <linux/input/mt.h>
#include <linux/serio.h>
#include <linux/tty.h>
#include <linux/ioctl.h>
#include <linux/fb.h>
#include <linux/fs.h>
#include <linux/poll.h>
#include <linux/syscalls.h>
#include <linux/compat.h>
#include <linux/workqueue.h>


#define DRIVER_DESC	"TSH virtual input driver"

static int max_x = 0;
static int max_y = 0;
static int idx_fb = 0;
module_param(max_x, int, 0664);
module_param(max_y, int, 0664);
module_param(idx_fb, int, 0664);
MODULE_PARM_DESC(max_x, "ABS report max value for x(default is used fbX xres).");
MODULE_PARM_DESC(max_y, "ABS report max value for y(default is used fbX yres).");
MODULE_PARM_DESC(idx_fb, "Fb index be used for max xy value(default is 0).");

struct vinput_priv {
	struct input_dev *input_dev;
	char name[64];
	char phys[32];
};

static struct vinput_priv *vi;

static int __init vinput_init(void)
{
	int i, ret;
	vi = kzalloc(sizeof(struct vinput_priv), GFP_KERNEL);
	if(!vi) return -ENOMEM;

	vi->input_dev = input_allocate_device();
	if(!vi) {
		ret = -ENOMEM;
		goto err0;
	}

	if(num_registered_fb > idx_fb && (!max_x || !max_y)) {
		max_x = registered_fb[idx_fb]->var.xres;
		max_y = registered_fb[idx_fb]->var.yres;
	}

	if(!max_x || !max_y) {
		max_x = 4096;
		max_y = 4096;
	}

	vi->input_dev->evbit[BIT_WORD(EV_SYN)] |= BIT_MASK(EV_SYN);
	vi->input_dev->evbit[BIT_WORD(EV_KEY)] |= BIT_MASK(EV_KEY);
	vi->input_dev->evbit[BIT_WORD(EV_ABS)] |= BIT_MASK(EV_ABS);
	vi->input_dev->evbit[BIT_WORD(EV_REL)] |= BIT_MASK(EV_REL);
	vi->input_dev->evbit[BIT_WORD(EV_MSC)] |= BIT_MASK(EV_MSC);
	vi->input_dev->evbit[BIT_WORD(EV_LED)] |= BIT_MASK(EV_LED);
	vi->input_dev->evbit[BIT_WORD(EV_SND)] |= BIT_MASK(EV_SND);

	for (i = 0; i < 255; i++) {
		input_set_capability(vi->input_dev, EV_KEY, i);
	}

	input_set_capability(vi->input_dev, EV_KEY, BTN_LEFT);
	input_set_capability(vi->input_dev, EV_KEY, BTN_RIGHT);
	input_set_capability(vi->input_dev, EV_KEY, BTN_MIDDLE);
	input_set_capability(vi->input_dev, EV_KEY, BTN_TOUCH);
	input_set_capability(vi->input_dev, EV_ABS, ABS_X);
	input_set_capability(vi->input_dev, EV_ABS, ABS_Y);
	input_set_capability(vi->input_dev, EV_ABS, ABS_PRESSURE);
	input_set_capability(vi->input_dev, EV_ABS, ABS_MT_POSITION_X);
	input_set_capability(vi->input_dev, EV_ABS, ABS_MT_POSITION_Y);
	input_set_capability(vi->input_dev, EV_ABS, ABS_MT_PRESSURE);
	input_set_capability(vi->input_dev, EV_ABS, ABS_MT_SLOT);
	input_set_capability(vi->input_dev, EV_REL, REL_X);
	input_set_capability(vi->input_dev, EV_REL, REL_Y);
	input_set_capability(vi->input_dev, EV_REL, REL_WHEEL);
	input_set_capability(vi->input_dev, EV_MSC, MSC_SCAN);
	input_set_capability(vi->input_dev, EV_LED, LED_NUML);
	input_set_capability(vi->input_dev, EV_LED, LED_CAPSL);
	input_set_capability(vi->input_dev, EV_LED, LED_SCROLLL);
	input_set_capability(vi->input_dev, EV_SND, SND_BELL);

    input_set_abs_params(vi->input_dev, ABS_PRESSURE, 0, 255, 0, 0);
    input_set_abs_params(vi->input_dev, ABS_MT_TRACKING_ID, 0, 255, 0, 0);
    input_set_abs_params(vi->input_dev, ABS_MT_PRESSURE, 0, 255, 0, 0);
    input_set_abs_params(vi->input_dev, ABS_MT_SLOT, 0, 9, 0, 0);

    sprintf(vi->phys, "input/tsh_vinput");
	strcpy(vi->name, DRIVER_DESC);
    vi->input_dev->name = vi->name;
    vi->input_dev->phys = vi->phys;
    vi->input_dev->id.bustype = BUS_I2C;
    vi->input_dev->id.vendor = 0x5354;
    vi->input_dev->id.product = 0x0001;
    vi->input_dev->id.version = 0x0100;
	input_set_drvdata(vi->input_dev, vi);

	input_mt_init_slots(vi->input_dev, 10, INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED);
	input_set_abs_params(vi->input_dev, ABS_X, 0, max_x, 0, 0);
	input_set_abs_params(vi->input_dev, ABS_Y, 0, max_y, 0, 0);
    input_set_abs_params(vi->input_dev, ABS_MT_POSITION_X, 0, max_x, 0, 0);
    input_set_abs_params(vi->input_dev, ABS_MT_POSITION_Y, 0, max_y, 0, 0);
    input_set_abs_params(vi->input_dev, ABS_MT_WIDTH_MAJOR, 0, 255, 0, 0);
    input_set_abs_params(vi->input_dev, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0);

    ret = input_register_device(vi->input_dev);
	if(ret) {
		ret = -ENODEV;
		goto err1;
	}

	return 0;
err1:
	input_free_device(vi->input_dev);
err0:
	kfree(vi);
	return ret;
}

static void __exit vinput_exit(void)
{
	input_unregister_device(vi->input_dev);
	input_mt_destroy_slots(vi->input_dev);
	input_free_device(vi->input_dev);
	kfree(vi);
}

module_init(vinput_init);
module_exit(vinput_exit);

MODULE_AUTHOR("TSH, Inc. Lenver Shan <shannw@tamsong.com>");
MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_ALIAS("Input");
MODULE_LICENSE("GPL");

